package generator.workflow.sfj.gridgain;

import generator.datastructure.MethodDeclaration;
import generator.datastructure.WritebleClass;

import java.util.ArrayList;

/**Es la clase encargada de la generación de un Wrapper que representa un nuevo trabajo a ejecutar
 * en el Grid.<BR> Dicho wrapper contiene sólo dos métodos: el
 * constructor de la clase y el método call resultante de implementar la interface {@link java.util.concurrent.Callable}.
 * Dicho trabajo será invocado por el middleware GridGain y ejecutado en algún nodo del grid.
 * Existirá una clase Wrapper por cada método a gridificar indicado por el usuario.
 * El nombre del wrapper generado es una combinación del nombre de la clase que contiene el método
 * a gridificar junto con el nombre del método a gridificar propiamente dicho más un sufijo compuesto por las iniciales GJ que
 * corresponden a Grid Job.
 * @author Hirsch-Fernández
 */
public abstract class GridJob {

	
	/** Dada una declaración de método a gridificar y una clase peer ya construida, se obtiene la clase
	 * Wrapper Grid Job utilizada por la clase peer para la ejecución de nuevos trabajos en un grid del
	 * el middleware de GridGain.
	 * Este método se compone de un conjunto de pasos puntuales que deberan ser definidos por el usuario
	 * de acuerdo a la representación de clases que utilice.
	 * @param md
	 * 			Objeto que representa la declaración de método a gridificar
	 * @param gridifiedClass
	 * 			Objeto que representa la clase peer donde se encuentra el método gridificado
	 * @return objeto que representa la clase Grid Job perteneciente al método gridificado.
	 */	
	public WritebleClass getGridJobClass(MethodDeclaration md, WritebleClass gridifiedClass){
		
		this.startNewGridJobClass();
		
		this.setClassPackage(this.extractPackageName(md));
		this.setClassImports(gridifiedClass);
		this.setGJName(md);
		this.setGRRefences(md);
		this.setJobType(md);
		ArrayList<String> params = md.getParametersType();
		for (int i = 0; i < params.size(); i++) {
			String paramType = params.get(i);
			this.registerNewParam(md,paramType,"param"+i);
			this.setCastType(md);
			this.setCalledMethod(md);
		}
		return this.getGJ();
	}	

	/**Dado una declaración de un método, se extrae el paquete de la clase que posee al mismo
	 * El método es utilizado en el paso de seteo del paquete al cual pertenecerá la clase Wrapper.
	 * @param fullClassName
	 * 			nombre completo de la clase que contiene el método a gridificar
	 * @return nombre del paquete al que pertenecerá la clase Wrapper*/
	public String extractPackageName(MethodDeclaration md){
		String packName = md.getClassOwner();
		if(packName.lastIndexOf(".")>0)
			return packName.substring(0, packName.lastIndexOf("."));
		else
			return "";
	}
	
	/**Arma el nombre de la clase Wrapper en base al nombre de la clase que contiene el método a 
	 * gridificar y el nombre del método a gridificar propiamente dicho.
	 * @param md
	 * 			declaración del método a gridificar del cual se utilizará su nombre y la clase a
	 * 			la que pertenece para armar el nombre de la clase Wrapper
	 * @return nombre que recibirá la clase Wrapper*/
	public static String getGJName(MethodDeclaration md){
		return getClassName(md)+"_" + md.getMethodName() + "GJ";	
	}
	
	/** Devuelve el nombre de la clase que contiene el método pasado como parametro. 
	 * @param md declaración de un método
	 * @return nombre de la clase que posee el método recibido como parametro
	 */
	public static String getClassName(MethodDeclaration md){
		String fullName = md.getClassOwner();
		if(fullName.lastIndexOf(".")>0)
			//en caso de que el nombre de la clase a la que pertenece md contiene nombres de paquetes
			fullName = fullName.substring(fullName.lastIndexOf(".")+1);
		return fullName;
	}
	
	
	/** 1) Método donde se genera una nueva instancia de clase Grid Job. */
	protected abstract void startNewGridJobClass();
	
	/** 2) Agregar el package donde se va a incluir el archico GridJob. */
	protected abstract void setClassPackage(String md);
	
	/** 3) Agregar los import necesarios en el GridJob. */
	protected abstract void setClassImports(WritebleClass gridifiedClass);
	
	/** 4) Modifica el nombre de la clase GJ.*/
	protected abstract void setGJName(MethodDeclaration md);
	
	/** 5) Modifica las referencias al GridResult.*/
	protected abstract void setGRRefences(MethodDeclaration md);
	
	 /** 6) Modificar el tipo de la variable de clase job (en la variable de instancia y en el método constructor) 
	  * segun el nombre de la clase que poseé el método a gridificar. Tambien se agrega el parametro del constructor
	  * que hace referencia al nivel de recursión máximo*/
	protected abstract void setJobType(MethodDeclaration md);
	
	/** 7) Para cada uno de los parametros del método a gridificar se agregan las variables de instancia,
	 *    se incorporan los parametros del método constructor, se realizar la asignacion de los parametros del constructor 
	 *    a las variables de clase y se agregan los argumentos para el llamado del método a gridificar dentro del método call
	 */
	protected abstract void registerNewParam(MethodDeclaration md,String paramType,String paramName);
	 
	/** 8) Se modifica el llamado del método de acuerdo a el tipo que devuelve */
	protected abstract void setCastType(MethodDeclaration md);
	
	/** 9)Se modifica el nombre del método gridificado*/
	protected abstract void setCalledMethod(MethodDeclaration md);
	
	/** 10) Método encargado de devolver la clase Wrapper de un nuevo Grid Job adecuadamente modificado.
	 * @return la clase Wrapper con las modificaciones realizadas. */	
	protected abstract WritebleClass getGJ();
}
